#include "CS6360Assignment1.h"

#define 	WINDOW_WIDTH 1000
#define 	WINDOW_HEIGHT 600

enum stereo_t {
	STEREO_NONE,
	STEREO_RIGHT,
	STEREO_LEFT
};

static GLfloat* cur_offset = 0;
GLfloat diffuseMaterial[4] = { 0.5, 0.5, 0.5, 1.0 };
bool do_exit = false;
int	main_window;
int window_x;
int window_y;
int window_width;
int window_height;
// the camera info  
float eye[3];
float lookat[3];
float aspectRatio;


// pointers for all of the glui controls
GLUI *glui;
GLUI_Rollout		*object_rollout;
GLUI_Rollout		*projection_rollout;
GLUI_RadioGroup		*object_type_radio;
GLUI_RadioGroup		*projection_radio;
GLUI_Translation	*nearplane_trans;
GLUI_Spinner		*nearplane_spin;
GLUI_Translation	*focal_length_trans;
GLUI_Spinner		*focal_length_spin;
GLUI_Translation	*farplane_trans;
GLUI_Spinner		*farplane_spin;
GLUI_Translation	*light_intensity_trans;
GLUI_Spinner		*light_intensity_spin;
GLUI_Translation	*IOD_trans;
GLUI_Spinner		*IOD_spin;
GLUI_Translation	*FOVY_trans;
GLUI_Spinner		*FOVY_spin;

GLUI_Rollout		*anim_rollout;
GLUI_Button			*action_button;
GLUI_Button			*picking_button;

GLUI_Checkbox *draw_floor_check;
GLUI_Checkbox *draw_object_check;

// This  checkbox utilizes the callback
GLUI_Checkbox *use_depth_buffer;


// the user id's that we can use to identify which control
// caused the callback to be called
#define CB_DEPTH_BUFFER 0
#define CB_ACTION_BUTTON 1
#define CB_RESET 2

// walking action variables
//
GLfloat step = 0;
GLfloat live_anim_speed = 3;

// live variables
// each of these are associated with a control in the interface.
// when the control is modified, these variables are automatically updated
int live_object_type;	// 0=carosel
int live_projection_type;
float live_object_rotation[16];
float live_nearplane_trans;
float live_focal_length;
float live_farplane_trans;
float live_IOD_trans;
float live_FOVY_trans;
float live_light_intensity_trans;

int live_draw_floor;
int live_draw_object;
int live_stereo_view;
int live_prevent_floor_culling;
int live_use_depth_buffer;
static int debug = 1;
static int use_flashlight = 0;
int draw_on_idle = 1;


void crossproduct(float a[3], float b[3], float res[3])
{
	res[0] = (a[1] * b[2] - a[2] * b[1]);
	res[1] = (a[2] * b[0] - a[0] * b[2]);
	res[2] = (a[0] * b[1] - a[1] * b[0]);
}

float length(float v[3])
{
	return (float)sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
}

void normalize(float v[3])
{
	float l = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
	l = 1 / (float)sqrt(l);

	v[0] *= l;
	v[1] *= l;
	v[2] *= l;
}

void myGlutIdle(void)
{
	// make sure the main window is active
	if (glutGetWindow() != main_window)
		glutSetWindow(main_window);

	// if you have moving objects, you can do that here
	//update carosel turning, if necessary
	if(step  == 0) {
		//carosel_angle += live_anim_speed;
	}
	//re-synchronize all the live variables
	glui->sync_live();

	// just keep redrawing the scene over and over
	if(draw_on_idle)
		glutPostRedisplay();
	
}


// mouse handling functions for the main window
// left mouse translates, middle zooms, right rotates
// keep track of which button is down and where the last position was
int cur_button = -1;
int last_x;
int last_y;

// catch mouse up/down events
void myGlutMouse(int button, int state, int x, int y)
{
	//printf("myGlutMouse called with %d, %d, %d, %d.\n", button, state, x, y);
	if(step != -2) { //we are not in picking mode
		if (state == GLUT_DOWN)
			cur_button = button;
		else
		{
			if (button == cur_button)
				cur_button = -1;
		}
	
		last_x = x;
		last_y = y;
	} else {
		if(state == GLUT_DOWN) {
			last_x = x;
			last_y = y;
		}
	}
	//printf("myGlutMouse done.\n");
}

// catch mouse move events
void myGlutMotion(int x, int y)
{
	//printf("GlutMotion: %d %d", x, y);
	//fprintf(stderr, "myGlutMotion called with %d, %d in mode %d\n", x, y, step);
	// the change in mouse position
	int dx = x-last_x;
	int dy = y-last_y;
	//printf(" dx dy: %d,%d", dx, dy);
	if(step == -2) {
		//printf("Horse Offset: %f %f\n", horse_offset[0], horse_offset[1]);
		cur_offset[0] -= ((GLfloat)dx) / 50.0;
		cur_offset[1] -= ((GLfloat)dy) / 50.0;
	}
	
	float scale, len, theta;
	float neye[3], neye2[3];
	float f[3], r[3], u[3];
	 //in normal mode
		switch(cur_button)
		{
		case GLUT_LEFT_BUTTON:
			// translate
			f[0] = lookat[0] - eye[0];
			f[1] = lookat[1] - eye[1];
			f[2] = lookat[2] - eye[2];
			u[0] = 0;
			u[1] = 1;
			u[2] = 0;
	
			// scale the change by how far away we are
			scale = sqrt(length(f)) * 0.007;
	
			crossproduct(f, u, r);
			crossproduct(r, f, u);
			normalize(r);
			normalize(u);
	
			eye[0] += -r[0]*dx*scale + u[0]*dy*scale;
			eye[1] += -r[1]*dx*scale + u[1]*dy*scale;
			eye[2] += -r[2]*dx*scale + u[2]*dy*scale;
	
			lookat[0] += -r[0]*dx*scale + u[0]*dy*scale;
			lookat[1] += -r[1]*dx*scale + u[1]*dy*scale;
			lookat[2] += -r[2]*dx*scale + u[2]*dy*scale;
	
			break;
	
		case GLUT_MIDDLE_BUTTON:
			// zoom
			f[0] = lookat[0] - eye[0];
			f[1] = lookat[1] - eye[1];
			f[2] = lookat[2] - eye[2];
	
			len = length(f);
			normalize(f);
	
			// scale the change by how far away we are
			len -= sqrt(len)*dx*0.03;
	
			eye[0] = lookat[0] - len*f[0];
			eye[1] = lookat[1] - len*f[1];
			eye[2] = lookat[2] - len*f[2];
	
			// make sure the eye and lookat points are sufficiently far away
			// push the lookat point forward if it is too close
			if (len < 1)
			{
				printf("lookat move: %f\n", len);
				lookat[0] = eye[0] + f[0];
				lookat[1] = eye[1] + f[1];
				lookat[2] = eye[2] + f[2];
			}
	
			break;
	
		case GLUT_RIGHT_BUTTON:
			// rotate
	
			neye[0] = eye[0] - lookat[0];
			neye[1] = eye[1] - lookat[1];
			neye[2] = eye[2] - lookat[2];
	
			// first rotate in the x/z plane
			theta = -dx * 0.007;
			neye2[0] = (float)cos(theta)*neye[0] + (float)sin(theta)*neye[2];
			neye2[1] = neye[1];
			neye2[2] =-(float)sin(theta)*neye[0] + (float)cos(theta)*neye[2];
	
	
			// now rotate vertically
			theta = -dy * 0.007;
	
			f[0] = -neye2[0];
			f[1] = -neye2[1];
			f[2] = -neye2[2];
			u[0] = 0;
			u[1] = 1;
			u[2] = 0;
			crossproduct(f, u, r);
			crossproduct(r, f, u);
			len = length(f);
			normalize(f);
			normalize(u);
	
			neye[0] = len * ((float)cos(theta)*f[0] + (float)sin(theta)*u[0]);
			neye[1] = len * ((float)cos(theta)*f[1] + (float)sin(theta)*u[1]);
			neye[2] = len * ((float)cos(theta)*f[2] + (float)sin(theta)*u[2]);
	
			eye[0] = lookat[0] - neye[0];
			eye[1] = lookat[1] - neye[1];
			eye[2] = lookat[2] - neye[2];
	
			//printf("Eye: %f %f %f\n", eye[0], eye[1], eye[2]);
			//printf("Lookat: %f %f %f\n", lookat[0], lookat[1], lookat[2]);
			break;
		}
	
	

	last_x = x;
	last_y = y;


	//render_to_env_map();
	glutPostRedisplay();
}

// you can put keyboard shortcuts in here
void myGlutKeyboard(unsigned char key, int x, int y)
{
	switch(key)
	{
	// quit
	case 27: 
	case 'q':
	case 'Q':
		do_exit = true;
		break;
	case 's':
		toggle_action();
		glutPostRedisplay();
		break;
	}

	glutPostRedisplay();
}

// the window has changed shapes, fix ourselves up
void myGlutReshape(int	x, int y)
{
	GLUI_Master.get_viewport_area(&window_x, &window_y, &window_width, &window_height);
	aspectRatio = (float)window_width / (float)window_height;
	glutPostRedisplay();
}

void draw_object(void)
{
	if(live_draw_object) {
		glPushMatrix();
		glTranslatef(0, 2.0, 0);
		glColor3f(0.7, 0.4, 0.6);
		switch(live_object_type){
			case 0:
				glutSolidTeapot(1.0);
				break;
			case 1:
				glutSolidCone(1.0, 2.0, 20, 20);
				break;
			case 2:
				glutSolidDodecahedron();
				break;
			case 3:
				glutSolidIcosahedron();
				break;
			case 4:
				glutSolidSphere(1.0, 20, 20);
				break;
			case 5:
				glutSolidTorus(1.0, 2.0, 20, 20);
				break;
		}
		glPopMatrix();
	}
	
	if (live_draw_floor)
	{
		/*glBegin(GL_TRIANGLE_FAN);
		glColor3f(0.4f, 0.4f, 0.4f);    // note: color is state and only needs to be set once
		glNormal3f(0.0, 1.0, 0.0);
		glVertex3f(-10, 0, -10);
		glVertex3f( 10, 0, -10);
		glVertex3f( 10, 0,  10);
		glVertex3f(-10, 0,  10);
		glEnd();*/
		int check = 0;
		for(int i = -10; i < 10; i++) {
			check++;
			for(int j = -10; j < 10; j++) {
				check++;
				if(check % 2) {
					glColor3f(0, 0, 1);
				}
				else {
					glColor3f(0, 1, 0);
				}
				glBegin(GL_TRIANGLE_FAN);
				glVertex3f(j - 0.5, 0, i - 0.5);
				glVertex3f(j + 0.5, 0, i - 0.5);
				glVertex3f(j + 0.5, 0, i + 0.5);
				glVertex3f(j - 0.5, 0, i + 0.5);
				glEnd();
			}
		}
		glEnd();
	}
}

void set_projection(stereo_t stereo)
{
	float frustumShift = live_IOD_trans / 2 * live_nearplane_trans / live_focal_length;
	float top = tan(live_FOVY_trans/2)*live_nearplane_trans;
	float bottom = -1 * top;
	float left = bottom * aspectRatio;
	float right = top * aspectRatio;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	switch(live_projection_type) {
		case 0: //glOrtho
			glOrtho(-1, 1, -1, 1, live_nearplane_trans, live_farplane_trans);
			break;
		case 1: //gluPerspective
			gluPerspective(90.0, 1.0, live_nearplane_trans, live_farplane_trans);
			break;
		case 2: //glFrustum
			if(stereo == STEREO_RIGHT) {
				glFrustum(left - frustumShift, right - frustumShift, bottom, top, live_nearplane_trans, live_farplane_trans);
			} else if(stereo == STEREO_LEFT) {
				glFrustum(left + frustumShift, right + frustumShift, bottom, top, live_nearplane_trans, live_farplane_trans);
			} else {
				glFrustum(-1, 1, -1, 1, live_nearplane_trans, live_farplane_trans);
			}
			break;
	}
}

void set_camera(stereo_t stereo)
{
		// camera transform
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	if(stereo == STEREO_RIGHT) {
		gluLookAt(eye[0] + live_IOD_trans / 2.0, eye[1], eye[2], lookat[0], lookat[1], lookat[2], 0, 1, 0);
	} else if (stereo == STEREO_LEFT) {
		gluLookAt(eye[0] - live_IOD_trans / 2.0, eye[1], eye[2], lookat[0], lookat[1], lookat[2], 0, 1, 0);
	} else {
		gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], 0, 1, 0);
	}
}

// draw the scene
void myGlutDisplay(	void )
{
	//clear the buffers
	glClearColor(0, 0, 0, 0);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	//re-set the light
	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 100 / live_light_intensity_trans );
	
	//set up the viewport
	if(live_stereo_view) {
		glViewport(window_x, 0, window_width / 2, window_height);
		set_projection(stereo_t::STEREO_LEFT);
		set_camera(stereo_t::STEREO_LEFT);
		draw_object();

		glViewport(window_x + (window_width / 2), 0, window_width / 2, window_height);
		set_projection(stereo_t::STEREO_RIGHT);
		set_camera(stereo_t::STEREO_RIGHT);
		draw_object();
	} else {
		glViewport(window_x, window_y, window_width, window_height);
		set_projection(stereo_t::STEREO_NONE);
		set_camera(stereo_t::STEREO_NONE);
		draw_object();
	}
	

	
	if(draw_on_idle)
		glutSwapBuffers(); 
}

void toggle_action(void)
{
	if (step < 0)
			{
				step = 0;
				action_button->set_name("Stop");
			}
			else
			{
				step = -1;
				action_button->set_name("Start");
			}
}

// some controls generate a callback when they are changed
void glui_cb(int control)
{

	switch(control)
	{
	case CB_DEPTH_BUFFER:
		if (live_use_depth_buffer)
			glEnable(GL_DEPTH_TEST);
		else
			glDisable(GL_DEPTH_TEST);
		break;
	case CB_ACTION_BUTTON:
		toggle_action();
		break;
	case CB_RESET:
		initialize_live_variables();
		break;
	}

	glutPostRedisplay();
}

void initialize_live_variables(void)
{
	live_object_type = 0;
	live_light_intensity_trans = 100.0;
	live_draw_floor = 1;
	live_draw_object = 1;
	live_stereo_view = 0;
	live_projection_type = 1;

	live_nearplane_trans = 1.0;
	live_focal_length = 40.0;
	live_farplane_trans = 100.0;
	live_IOD_trans = 0.5;
	live_FOVY_trans = 90.0;

	for(int i = 0; i < 15; i++)
		live_object_rotation[i] = 0;
	live_object_rotation[0] = 1;
	live_object_rotation[5] = 1;
	live_object_rotation[10] = 1;
	live_object_rotation[15] = 1;
		
}
// entry point
int main(int argc,	char* argv[])
{
	
	//
	// create the glut window
	//
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutInitWindowPosition(100,100);
	main_window = glutCreateWindow("CS6360 Assignment 1");
	init();
	
	//
	// set callbacks
	//
	glutDisplayFunc(myGlutDisplay);
	GLUI_Master.set_glutReshapeFunc(myGlutReshape);
	GLUI_Master.set_glutIdleFunc(myGlutIdle);
	GLUI_Master.set_glutKeyboardFunc(myGlutKeyboard);
	GLUI_Master.set_glutMouseFunc(myGlutMouse);
	glutMotionFunc(myGlutMotion);


	//
	// create the interface subwindow and add widgets
	//
	glui = GLUI_Master.create_glui_subwindow(main_window, GLUI_SUBWINDOW_LEFT);
	GLUI_Master.get_viewport_area(&window_x, &window_y, &window_width, &window_height);
	

	// initialize live variables
	initialize_live_variables();

	// quit button
	glui->add_button("Quit", 0, (GLUI_Update_CB)exit);
	
	// empty space
	glui->add_statictext("");

	// the object rollout
	object_rollout = glui->add_rollout("Object");
	projection_rollout = glui->add_rollout("Projection");
	
	// the radio buttons
	object_type_radio = glui->add_radiogroup_to_panel(object_rollout, &live_object_type);
	glui->add_radiobutton_to_group(object_type_radio, "Teapot");
	glui->add_radiobutton_to_group(object_type_radio, "Cone");
	glui->add_radiobutton_to_group(object_type_radio, "Dodecahedron");
	glui->add_radiobutton_to_group(object_type_radio, "Icosahedron");
	glui->add_radiobutton_to_group(object_type_radio, "Sphere");
	glui->add_radiobutton_to_group(object_type_radio, "Torus");
	projection_radio = glui->add_radiogroup_to_panel(projection_rollout, &live_projection_type);
	glui->add_radiobutton_to_group(projection_radio, "glOrtho");
	glui->add_radiobutton_to_group(projection_radio, "gluPerspective");
	glui->add_radiobutton_to_group(projection_radio, "glFrustum");
	
	// rotation and translation controls
	// we need an extra panel to keep things grouped properly
	GLUI_Panel *transform_panel = glui->add_panel_to_panel(object_rollout, "", GLUI_PANEL_NONE);
	//object_rotation = glui->add_rotation_to_panel(transform_panel, "Rotation", live_object_rotation);
	//object_rotation->reset();

	glui->add_column_to_panel(transform_panel, false);
	nearplane_spin = glui->add_spinner_to_panel(transform_panel, "Near", GLUI_SPINNER_FLOAT, &live_nearplane_trans);
	farplane_spin = glui->add_spinner_to_panel(transform_panel, "Far", GLUI_SPINNER_FLOAT, &live_farplane_trans);
	focal_length_spin = glui->add_spinner_to_panel(transform_panel, "Focal", GLUI_SPINNER_FLOAT, &live_focal_length);
	light_intensity_spin = glui->add_spinner_to_panel(transform_panel, "Light", GLUI_SPINNER_FLOAT, &live_light_intensity_trans);
	IOD_spin = glui->add_spinner_to_panel(transform_panel, "IOD", GLUI_SPINNER_FLOAT, &live_IOD_trans);
	FOVY_spin = glui->add_spinner_to_panel(transform_panel, "FOVY", GLUI_SPINNER_FLOAT, &live_FOVY_trans);
	
	glui->add_column_to_panel(transform_panel, false);
	nearplane_trans = glui->add_translation_to_panel(transform_panel, "Near Plane", 
													 GLUI_TRANSLATION_Y, &live_nearplane_trans);
	nearplane_trans->scale_factor = 0.1;
	farplane_trans = glui->add_translation_to_panel(transform_panel, "Far Plane", 
													 GLUI_TRANSLATION_Y, &live_farplane_trans);
	focal_length_trans = glui->add_translation_to_panel(transform_panel, "Focal Length", GLUI_TRANSLATION_Y, &live_focal_length);
	
	
	glui->add_column_to_panel(transform_panel, false);
	light_intensity_trans = glui->add_translation_to_panel(transform_panel, "Light Intensity", 
														 GLUI_TRANSLATION_Y, &live_light_intensity_trans);
	IOD_trans = glui->add_translation_to_panel(transform_panel, "IOD",
											GLUI_TRANSLATION_Y, &live_IOD_trans);
	FOVY_trans = glui->add_translation_to_panel(transform_panel, "FOVY", GLUI_TRANSLATION_Y, &live_FOVY_trans);	
	FOVY_trans->scale_factor = 0.01;

	
	//object_y_trans =  glui->add_translation_to_panel(transform_panel, "Translate Y", 
	//												 GLUI_TRANSLATION_Y, &live_object_y_trans);
	
	// empty space
	glui->add_statictext("");

	// our checkbox options for deciding what to draw
	glui->add_checkbox("Draw Floor", &live_draw_floor);
	glui->add_checkbox("Draw Object", &live_draw_object);
	glui->add_checkbox("Stereo View", &live_stereo_view);
	// empty space
	glui->add_statictext("");

	
	glui->set_main_gfx_window(main_window);

	// initialize the camera
	eye[0] = 0;
	eye[1] = 4;
	eye[2] = 10;
	
		
	lookat[0] = 0;
	lookat[1] = 0;
	lookat[2] = 0;
	
		

	// initialize gl
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);
	glInitNames();
	//map the cube map

	// give control over to glut
	glutMainLoop();

	return 0;
}



void init(void)
{
	GLfloat light_position[] = { 1.0, 1.0, 0.0, 1.0 };
	GLfloat white_light[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat lmodel_ambient[] = {0.1, 0.1, 0.1, 1.0 };
	
	glEnable(GL_TEXTURE_2D);
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_SMOOTH);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);

	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
	glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
	glEnable(GL_LIGHT0);
	
	
		
}


	
